// SPDX-FileCopyrightText: 2023 CASTest Corporation Limited
// SPDX-License-Identifier: LGPL-v3

#include "atpg/ATPGCommons.h"

namespace ictest {

/**
 * @brief
 * Identify Fanout-free region
 */
void ATPGCommons::FindFreeFanoutRegion() {
  gate_ffr_id_.resize(prim_->NumGates());
  std::queue<Gate *> que;
  int ffr_id = 0;
  FreeFanoutRegion *ffr = nullptr;
  std::vector<Gate *> ffr_output_gate;

  for (auto po : prim_->GetPOGates()) {
    ffr = new FreeFanoutRegion;
    ffr->id = ffr_id;
    ffr->stem = po;

    free_fanout_regions_.emplace_back(ffr);
    gate_ffr_id_[po->GetGId()] = ffr_id;
    ffr_id++;
    ffr_output_gate.emplace_back(po);
  }

  for (auto gate : prim_->GetPrimNetlist()) {
    if (gate->GetGType() == GType::G_XOR || gate->GetGType() == GType::G_XNOR ||
        gate->GetGType() == GType::G_MUX || gate->GetGType() == GType::G_DLAT) {
      for (auto fanin : gate->FaninGates()) {
        ffr = new FreeFanoutRegion;
        ffr->id = ffr_id;
        ffr->stem = fanin;
        free_fanout_regions_.emplace_back(ffr);
        gate_ffr_id_[fanin->GetGId()] = ffr_id;
        ffr_id++;

        ffr_output_gate.emplace_back(fanin);
      }
    }
  }

  for (auto &scan_chain : prim_->ScanChains()) {
    for (auto scan_cell : scan_chain->ScanChainDFFs()) {
      for (auto ppo : scan_cell->FaninGates()) {
        ffr = new FreeFanoutRegion;
        ffr->id = ffr_id;
        ffr->stem = ppo;
        free_fanout_regions_.emplace_back(ffr);
        gate_ffr_id_[ppo->GetGId()] = ffr_id;
        ffr_id++;
        ffr_output_gate.emplace_back(ppo);
      }
    }
  }

  for (auto stem : prim_->GetStemGates()) {
    ffr = new FreeFanoutRegion;
    ffr->id = ffr_id;
    ffr->stem = stem;
    free_fanout_regions_.emplace_back(ffr);
    gate_ffr_id_[stem->GetGId()] = ffr_id;
    ffr_id++;
    ffr_output_gate.emplace_back(stem);
  }

  for (auto stem : ffr_output_gate) {
    que.push(stem);
    ffr_id = gate_ffr_id_[stem->GetGId()];
    ffr = free_fanout_regions_[ffr_id];

    while (!que.empty()) {
      auto gate = que.front();
      que.pop();

      gate_ffr_id_[gate->GetGId()] = ffr_id;
      if (gate->GetGType() != GType::G_BRH &&
          gate->GetGType() != GType::G_DFF &&
          gate->GetGType() != GType::G_XOR &&
          gate->GetGType() != GType::G_XNOR &&
          gate->GetGType() != GType::G_MUX &&
          gate->GetGType() != GType::G_DLAT) {
        for (auto fanin : gate->FaninGates()) {
          que.push(fanin);
        }
      } else {
        if (gate->GetGType() != GType::G_DFF) {
          for (auto fanin : gate->FaninGates()) {
            FreeFanoutRegion *input_ffr =
                free_fanout_regions_[gate_ffr_id_[fanin->GetGId()]];
            ffr->fanins.emplace_back(input_ffr);
            input_ffr->fanouts.emplace_back(ffr);
          }
        }
      }
    }
  }

  int count = 0;
  for (auto item : free_fanout_regions_) {
    if (item->fanouts.empty()) {
      po_ids_[item->id] = count;
      count++;
    }
  }
}

/**
 * @brief
 * Get sequential info of DFF / DLATCH
 */
void ATPGCommons::GetRegisterInfo() {
  is_falling_edge_dff_.assign(prim_->NumGates(), false);
  is_seq_element_.assign(prim_->NumGates(), false);
  for (const auto &scan_chain : prim_->ScanChains()) {
    for (const auto &sc_dff : scan_chain->ScanChainDFFs()) {
      is_seq_element_[sc_dff->GetGId()] = true;
      if (sim_->simData_->gate_classify_mark_[sc_dff->GetGId()] ==
          GateClassify::FALLING_SC_DFF) {
        is_falling_edge_dff_[sc_dff->GetGId()] = true;
      }
    }
  }
  for (const auto &nsc_dff : prim_->GetNonScanDFFGates()) {
    LOG_ASSERT(nsc_dff->FaninSize() != 4, "Fanin Size of Non-scan DFF != 4")
    is_seq_element_[nsc_dff->GetGId()] = true;
    no_assign_[nsc_dff->GetGId()] = true;
    if (sim_->simData_->gate_classify_mark_[nsc_dff->GetGId()] ==
        GateClassify::ACT_FALLING_NSC) {
      is_falling_edge_dff_[nsc_dff->GetGId()] = true;
    }
  }
  for (const auto &dlatch : prim_->GetDLATGates()) {
    if (dlatch->FaninSize() != 4) {
      no_assign_[dlatch->GetGId()] = true;
      continue;
    }
    if (is_clk_cone_[dlatch->FaninGates()[0]->GetGId()] ||
        is_clk_cone_[dlatch->FaninGates()[1]->GetGId()] ||
        is_clk_cone_[dlatch->FaninGates()[2]->GetGId()]) {
      is_seq_element_[dlatch->GetGId()] = true;
      no_assign_[dlatch->GetGId()] = true;
    }
  }
}

/**
 * @brief
 * Mark clock effect cone
 * @details
 * Search from clock, stop when encountering DFF/DLATCH
 */
void ATPGCommons::MarkClkCone() {
  // 标记函数
  is_clk_cone_.assign(prim_->NumGates(), false);
  std::queue<Gate *> que;
  for (auto &clk : prim_->GetClkGates()) {
    que.push(clk);
    is_clk_cone_[clk->GetGId()] = true;
  }
  while (!que.empty()) {
    Gate *gate = que.front();
    que.pop();
    for (auto fanout : gate->FanoutGates()) {
      if (fanout->GetGType() == GType::G_DFF ||
          fanout->GetGType() == GType::G_DLAT) {
        continue;
      }
      if (!is_clk_cone_[fanout->GetGId()]) {
        is_clk_cone_[fanout->GetGId()] = true;
        que.push(fanout);
      }
    }
  }
}
} // namespace ictest